package com.feige.jwt;

import com.feige.constant.Constants;
import com.feige.utils.string.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author feige
 */

@Component
@Slf4j
@AllArgsConstructor
public class JwtTokenHelper implements TokenHelper {
    
    private final JwtProperties jwtProperties;


    public String getAppSecret(){
        String appSecret = jwtProperties.getAppSecret();
        if (StringUtils.isBlank(appSecret)){
            appSecret = Constants.APP_SECRET;
        }
        return appSecret;
    }


    public Date getExpirationAt(){
        Long expiresAt = jwtProperties.getExpiresAt();
        if (expiresAt == null){
            expiresAt = Constants.TOKEN_EXPIRE;
        }
        return new Date(System.currentTimeMillis() + (expiresAt * 1000L));
    }
    
    
    public boolean canRefresh(Date expireDate){
        Long refreshTokenDuration = jwtProperties.getRefreshTokenDuration();
        if (refreshTokenDuration == null){
            refreshTokenDuration = Constants.REFRESH_TOKEN_DURATION;
        }
        long time = expireDate.getTime();
        return time - System.currentTimeMillis() <= refreshTokenDuration * 1000L;
    }
    
    
    private Key getKeyInstance() {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary(getAppSecret());
        return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());
    }
    
    @Override
    public String generateToken(Map<String, Object> claims) {
        JwtBuilder jwtBuilder = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject(UUID.randomUUID().toString())
                .setIssuer(jwtProperties.getIssuer())
                //颁发时间
                .setIssuedAt(new Date())
                //过期时间
                .setExpiration(getExpirationAt());
        
        for (Map.Entry<String, Object> entry : claims.entrySet()) {
            jwtBuilder.claim(entry.getKey(), entry.getValue());
        }
        
        return jwtBuilder
                .signWith(getKeyInstance(), SignatureAlgorithm.HS256)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
    }

    @Override
    public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
        Claims jwtClaims = getJwtClaims(getRealToken(request));
        Date expiration = jwtClaims.getExpiration();
        if (canRefresh(expiration)){
            String token = Jwts.builder()
                    .setClaims(jwtClaims)
                    //颁发时间
                    .setIssuedAt(new Date())
                    //过期时间
                    .setExpiration(getExpirationAt())
                    .signWith(getKeyInstance(), SignatureAlgorithm.HS256)
                    .compressWith(CompressionCodecs.GZIP)
                    .compact();
            response.setHeader(Constants.TOKEN_KEY, token);
        }else {
            response.setHeader(Constants.TOKEN_KEY, "");
        }
    }

    


    public Claims getJwtClaims(String token){
        Jws<Claims> claimsJws = Jwts.parserBuilder()
                .setSigningKey(getKeyInstance())
                .build()
                .parseClaimsJws(getRealToken(token));
        return claimsJws.getBody();
    }

    @Override
    public Map<String, Object> getClaims(String token) {
        Claims jwtClaims = getJwtClaims(token);
        return new HashMap<>(jwtClaims);
    }
    

    @Override
    public boolean isExpired(String token) {
        try {
            Claims jwtClaims = getJwtClaims(token);
            return jwtClaims.getExpiration().before(new Date());
        } catch (Exception e) {
            log.error("token已过期：", e);
            return true;
        }
    }
}
